package edu.hawaii.myisern.model;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import javax.xml.bind.JAXBException;
import org.xml.sax.SAXException;
import edu.hawaii.myisern.collaborations.jaxb.Collaboration;
import edu.hawaii.myisern.organizations.jaxb.Organization;
import edu.hawaii.myisern.researchers.jaxb.Researcher;
import edu.hawaii.myisern.xmldatabase.XmlLoader;

/**
 * 
 * Main Model for MyIsern, implemented as a singleton. Contain a XmlLoader object which maintain the
 * system's data.
 * 
 * @author Shaoxuan Zhang
 */
public class MyIsernModel {

  /** The singleton StackModel instance. */
  private static MyIsernModel theInstance = null;
  private XmlLoader myIsernLoader = null;

  /**
   * Initialize XmlLoader with 3 predefined xml files.
   * 
   * @throws JAXBException when JAXB fails.
   * @throws SAXException when SAX fails.
   */
  private MyIsernModel() throws JAXBException, SAXException {
    // this.myIsernLoader = new XmlLoader("isern");
    this.myIsernLoader = new XmlLoader(System.getProperty("user.home")
        + "/myisern/collaborations.xml", System.getProperty("user.home")
        + "/myisern/organizations.xml", System.getProperty("user.home")
        + "/myisern/researchers.xml");
    this.findAllAffiliatedResearchers();
  }

  /**
   * Get the single instance of MyIsernModel object.
   * 
   * @return A MyIsernModel.
   * @throws JAXBException when JAXB fails.
   * @throws SAXException when SAX fails.
   */
  public static synchronized MyIsernModel getInstance() throws JAXBException, SAXException {
    if (MyIsernModel.theInstance == null) {
      MyIsernModel.theInstance = new MyIsernModel();
    }
    return MyIsernModel.theInstance;
  }

  /**
   * A Compare class for Collaboration
   * 
   * @author Shaoxuan Zhang
   */
  public class CollaborationComparator implements Comparator<Collaboration> {
    /**
     * Compares its two Collaboration for order.
     * 
     * @param arg0 the first Collaboration to be compared.
     * @param arg1 the second Collaboration to be compared.
     * @return a negative integer, zero, or a positive integer as the first argument is less than,
     * equal to, or greater than the second.
     */
    public int compare(Collaboration arg0, Collaboration arg1) {
      return arg0.getName().compareTo(arg1.getName());
    }
  }

  /**
   * A Compare class for Organization
   * 
   * @author Shaoxuan Zhang
   */
  public class OrganizationComparator implements Comparator<Organization> {
    /**
     * Compares its two Organization for order.
     * 
     * @param arg0 the first Organization to be compared.
     * @param arg1 the second Organization to be compared.
     * @return a negative integer, zero, or a positive integer as the first argument is less than,
     * equal to, or greater than the second.
     */
    public int compare(Organization arg0, Organization arg1) {
      return arg0.getName().compareTo(arg1.getName());
    }
  }

  /**
   * A Compare class for Researcher
   * 
   * @author Shaoxuan Zhang
   */
  public class ResearcherComparator implements Comparator<Researcher> {
    /**
     * Compares its two Researcher for order.
     * 
     * @param arg0 the first Researcher to be compared.
     * @param arg1 the second Researcher to be compared.
     * @return a negative integer, zero, or a positive integer as the first argument is less than,
     * equal to, or greater than the second.
     */
    public int compare(Researcher arg0, Researcher arg1) {
      return arg0.getName().compareTo(arg1.getName());
    }
  }

  /**
   * Return a List of Collaborations
   * 
   * @return - A List of String.
   */
  public synchronized List<Collaboration> getCollaborationList() {
    Collections.sort(myIsernLoader.getCollaborations().getCollaboration(),
        new CollaborationComparator());
    return myIsernLoader.getCollaborations().getCollaboration();
  }

  /**
   * Return a List of Organizations
   * 
   * @return - A List of String.
   */
  public synchronized List<Organization> getOrganizationList() {
    Collections.sort(myIsernLoader.getOrganizations().getOrganization(),
        new OrganizationComparator());
    return myIsernLoader.getOrganizations().getOrganization();
  }

  /**
   * Return a List of Researchers
   * 
   * @return - A List of String.
   */
  public synchronized List<Researcher> getResearcherList() {
    Collections.sort(myIsernLoader.getResearchers().getResearcher(), new ResearcherComparator());
    return myIsernLoader.getResearchers().getResearcher();
  }

  /**
   * Return a Collaboration object with the given name.
   * 
   * @param name - String of the given name.
   * @return - Collaborations contains the specified Collaboration. null if not found.
   */
  public synchronized Collaboration getCollaboration(String name) {
    String id = XmlLoader.toUniqueId(name);
    return myIsernLoader.getCollaboration(id);
  }

  /**
   * Return a Organization object with the given name.
   * 
   * @param name - String of the given name.
   * @return - Organizations object contains the specified Organization. null if not found.
   */
  public synchronized Organization getOrganization(String name) {
    String id = XmlLoader.toUniqueId(name);
    return myIsernLoader.getOrganization(id);
  }

  /**
   * Return a Researcher object with the given name.
   * 
   * @param name - String of the given name.
   * @return - Researchers object contains the specified Researcher. null if not found.
   */
  public synchronized Researcher getResearcher(String name) {
    String id = XmlLoader.toUniqueId(name);
    return myIsernLoader.getResearcher(id);
  }

  /**
   * Add the given Organization object into the system.
   * 
   * @param organization the given Organization object.
   * @return the old Organization object with the same ID with the given one. Null if the ID does
   * not exist yet.
   */
  public synchronized boolean add(Organization organization) {
    if (this.myIsernLoader.add(organization) == null) {
      return true;
    }
    return false;
  }

  /**
   * Add the given Collaboration object into the system.
   * 
   * @param collaboration the given Collaboration object.
   * @return the old Collaboration object with the same ID with the given one. Null if the ID does
   * not exist yet.
   */
  public synchronized boolean add(Collaboration collaboration) {
    if (this.myIsernLoader.add(collaboration) == null) {
      return true;
    }
    return false;
  }

  /**
   * Add the given Researcher object into the system.
   * 
   * @param researcher the given Researcher object.
   * @return the old Researcher object with the same ID with the given one. Null if the ID does not
   * exist yet.
   */
  public synchronized boolean add(Researcher researcher) {
    if (this.myIsernLoader.add(researcher) == null) {
      this.addResearcherToOrganization(researcher);
      return true;
    }
    this.addResearcherToOrganization(researcher);
    return false;
  }

  /**
   * Remove the given id and its associated object.
   * 
   * @param name String of the ID
   * @return true if the id and its associated object is successfully removed. otherwise, false is
   * returned.
   */
  public synchronized boolean remove(String name) {
    String id = XmlLoader.toUniqueId(name);
    return this.myIsernLoader.remove(id);
  }

  /**
   * Return true if the given name of Organization is contained in the system.
   * 
   * @param name String of the given name.
   * @return true if the name is in the system.
   */
  public synchronized boolean hasOrganization(String name) {
    return this.myIsernLoader.hasOrganization(name);
  }

  /**
   * Return true if the given name of Collaboration is contained in the system.
   * 
   * @param name String of the given name.
   * @return true if the name is in the system.
   */
  public synchronized boolean hasCollaboration(String name) {
    return this.myIsernLoader.hasCollaboration(name);
  }

  /**
   * Return true if the given name of Researcher is contained in the system.
   * 
   * @param name String of the given name.
   * @return true if the name is in the system.
   */
  public synchronized boolean hasResearcher(String name) {
    return this.myIsernLoader.hasResearcher(name);
  }

  /**
   * Check the given Collaboration, return names of organizations that did not exist.
   * 
   * @param collaboration an Collaboration object.
   * @return a List of String
   */
  public synchronized List<String> getInvalidReference(Collaboration collaboration) {
    List<String> invalidReference = new ArrayList<String>();
    if (collaboration == null) {
      return invalidReference;
    }
    if (collaboration.getCollaboratingOrganizations() != null) {
      for (String name : collaboration.
              getCollaboratingOrganizations().getCollaboratingOrganization()) {
        if (!this.myIsernLoader.hasOrganization(name)) {
          System.out.print("Not find Organization [" + name + "]");
          invalidReference.add(name);
        }
      }
    }
    return invalidReference;
  }

  /**
   * Check the given organization, return names of researchers that did not exist.
   * 
   * @param organization an Organization object.
   * @return a List of String
   */
  public synchronized List<String> getInvalidReference(Organization organization) {
    List<String> invalidReference = new ArrayList<String>();
    if (organization == null) {
      return invalidReference;
    }
    if (!this.myIsernLoader.hasResearcher(organization.getContact())) {
      invalidReference.add(organization.getContact());
    }
    if (organization.getAffiliatedResearchers() != null) {
      for (String name : organization.getAffiliatedResearchers().getAffiliatedResearcher()) {
        if (!this.myIsernLoader.hasResearcher(name)) {
          invalidReference.add(name);
        }
      }
    }
    return invalidReference;
  }
  
  /**
   * Add the given to Researcher to an organizatio according to its organization field.
   * @param researcher the given organization.
   * @return true if successfully added, otherwise false.
   */
  public synchronized boolean addResearcherToOrganization(Researcher researcher) {
    if (researcher != null && researcher.getName() != null && researcher.getName().length() > 0 
        && researcher.getOrganization() != null && researcher.getOrganization().length() > 0) {
      String name = researcher.getName();
      Organization organization = this.myIsernLoader.
                        getOrganization(XmlLoader.toUniqueId(researcher.getOrganization()));
      if (organization == null) {
        return false;
      }
      else {
        if (!organization.getContact().equals(name) 
            && !organization.getAffiliatedResearchers().getAffiliatedResearcher().contains(name)) {
          organization.getAffiliatedResearchers().getAffiliatedResearcher().add(name);
        }
      }
    }
    return false;
  }
  
  /**
   * find all AffiliatedResearcher for all organizations.
   * @return false if some researchers cannot find their organization.
   */
  public synchronized boolean findAllAffiliatedResearchers() {
    boolean flag = true;
    for (Researcher researcher : this.myIsernLoader.getResearchers().getResearcher()) {
      flag = flag && this.addResearcherToOrganization(researcher);
    }
    return flag;
  }
  
  /**
   * Check the given researcher, return names of organizations that did not exist.
   * 
   * @param researcher an Researcher object.
   * @return a List of String
   */
  public synchronized List<String> getInvalidReference(Researcher researcher) {
    List<String> invalidReference = new ArrayList<String>();
    if (researcher == null) {
      return invalidReference;
    }
    if (!this.myIsernLoader.hasOrganization(researcher.getOrganization())) {
      invalidReference.add(researcher.getOrganization());
    }
    return invalidReference;
  }
  
  /**
   * Check if the information within the given Collaboration is valid.
   * @param collaboration the given valid
   * @return true if everything are right, otherwise false.
   */
  public synchronized boolean semanticCheck(Collaboration collaboration) {
    if (this.myIsernLoader.semanticCheck(collaboration).length() == 0) {
      return true;
    }
    return false;
  }
  
  /**
   * Check if the information within the given Organization is valid.
   * @param organization the given valid
   * @return true if everything are right, otherwise false.
   */
  public synchronized boolean semanticCheck(Organization organization) {
    if (this.myIsernLoader.semanticCheck(organization).length() == 0) {
      return true;
    }
    return false;
  }
  
  /**
   * Check if the information within the given Researcher is valid.
   * @param researcher the given valid
   * @return true if everything are right, otherwise false.
   */
  public synchronized boolean semanticCheck(Researcher researcher) {
    if (this.myIsernLoader.semanticCheck(researcher).length() == 0) {
      return true;
    }
    return false;
  }
  
  /**
   * Return a list of Researcher that is missing.
   * @return a Set of String
   */
  public synchronized Set<String> getMissingResearcher() {
    Set<String> missingList = new HashSet<String>();
    for (Organization organization : this.myIsernLoader.getOrganizations().getOrganization()) {
      missingList.addAll(this.getInvalidReference(organization));
    }
    return missingList;
  }
  
  /**
   * Return a list of Organization that is missing.
   * @return a Set of String
   */
  public synchronized Set<String> getMissingOrganization() {
    Set<String> missingList = new HashSet<String>();
    for (Collaboration collaboration : this.myIsernLoader.getCollaborations().getCollaboration()) {
      missingList.addAll(this.getInvalidReference(collaboration));
    }
    for (Researcher researcher : this.myIsernLoader.getResearchers().getResearcher()) {
      missingList.addAll(this.getInvalidReference(researcher));
    }
    return missingList;
  }
  
  /**
   * Return a list of violations within researchers.
   * @return a List of Map Entry.
   */
  public synchronized Set<Entry<String, String>> getResearcherViolations() {
    Map<String,String> violations = new HashMap<String,String>();
    for (Researcher researcher : this.myIsernLoader.getResearchers().getResearcher()) {
      String violation = this.myIsernLoader.semanticCheck(researcher);
      if (violation.length() > 0) {
        violations.put(researcher.getName(), violation);
      }
    }
    return violations.entrySet();
  }
  
  /**
   * Return a list of violations within collaborations.
   * @return a List of Map Entry.
   */
  public synchronized Set<Entry<String, String>> getCollaborationViolations() {
    Map<String,String> violations = new HashMap<String,String>();
    for (Collaboration collaboration : this.myIsernLoader.getCollaborations().getCollaboration()) {
      String violation = this.myIsernLoader.semanticCheck(collaboration);
      if (violation.length() > 0) {
        violations.put(collaboration.getName(), violation);
      }
    }
    return violations.entrySet();
  }
  
  /**
   * Return a list of violations within organizations.
   * @return a List of Map Entry.
   */
  public synchronized Set<Entry<String, String>> getOrganizationViolations() {
    Map<String,String> violations = new HashMap<String,String>();
    for (Organization organization : this.myIsernLoader.getOrganizations().getOrganization()) {
      String violation = this.myIsernLoader.semanticCheck(organization);
      if (violation.length() > 0) {
        violations.put(organization.getName(), violation);
      }
    }
    return violations.entrySet();
  }
}
